﻿#region File and License Information
/*
<File>
	<License>
		Copyright © 2009 - 2017, Daniel Vaughan. All rights reserved.
		This file is part of Calcium (http://calciumsdk.net), 
		which is released under the MIT License.
		See file /Documentation/License.txt for details.
	</License>
	<CreationDate>2012-05-23 10:47:09Z</CreationDate>
</File>
*/
#endregion

using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Media;

namespace Outcoder.UI.Xaml
{
	public class TextBoxBehaviors
	{
		#region Watermark Behavior

		public static readonly DependencyProperty WatermarkBehaviorProperty = DependencyProperty.RegisterAttached(
			"WatermarkBehavior",
			typeof(WatermarkBehavior),
			typeof(TextBoxBehaviors),
			null);

		public static readonly DependencyProperty WatermarkTextProperty = DependencyProperty.RegisterAttached(
			"WatermarkText",
			typeof(string),
			typeof(TextBoxBehaviors),
			new PropertyMetadata(HandleWatermarkTextChanged));

		public static string GetWatermarkText(DependencyObject dependencyObject)
		{
			return (string)dependencyObject.GetValue(WatermarkTextProperty);
		}

		public static void SetWatermarkText(DependencyObject dependencyObject, string value)
		{
			dependencyObject.SetValue(WatermarkTextProperty, value);
		}

		static void HandleWatermarkTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var textBox = d as TextBox;
			WatermarkBehavior behavior = GetOrCreateWatermarkBehavior(textBox);
			behavior.WatermarkText = e.NewValue.ToString();
		}

		static WatermarkBehavior GetOrCreateWatermarkBehavior(TextBox textBox)
		{
			var behavior = textBox.GetValue(WatermarkBehaviorProperty) as WatermarkBehavior;

			if (behavior == null)
			{
				behavior = new WatermarkBehavior();
				behavior.Attach(textBox);
				textBox.SetValue(WatermarkBehaviorProperty, behavior);
			}

			return behavior;
		}

		
		#endregion

		#region TextCleared

		static readonly DependencyProperty TextClearedProperty
			= DependencyProperty.RegisterAttached(
				"TextCleared",
				typeof(bool),
				typeof(TextBoxBehaviors),
				new PropertyMetadata(false));

		public static bool GetTextCleared(Control target)
		{
			return (bool)target.GetValue(TextClearedProperty);
		}

		public static void SetTextCleared(Control target, bool value)
		{
			target.SetValue(TextClearedProperty, value);
		}

		#endregion

		#region EnterKeyCommand

		static readonly DependencyProperty EnterKeyCommandBehaviorProperty
			= DependencyProperty.RegisterAttached(
				"EnterKeyCommandBehavior",
				typeof(EnterKeyCommandBehavior),
				typeof(TextBoxBehaviors),
				new PropertyMetadata(null));

		static readonly DependencyProperty EnterKeyCommandProperty 
			= DependencyProperty.RegisterAttached(
				"EnterKeyCommand",
				typeof(ICommand),
				typeof(TextBoxBehaviors),
				new PropertyMetadata(OnEnterKeyCommandChanged));


		public static ICommand GetEnterKeyCommand(TextBox target)
		{
			return (ICommand)target.GetValue(EnterKeyCommandProperty);
		}

		public static void SetEnterKeyCommand(TextBox target, ICommand value)
		{
			target.SetValue(EnterKeyCommandProperty, value);
		}

		static void OnEnterKeyCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			TextBox textBox = d as TextBox;
			if (textBox == null)
			{
				throw new InvalidOperationException("EnterKeyCommandBehavior can only be used on a TextBox.");
			}

			EnterKeyCommandBehavior behavior = GetOrCreateEnterKeyCommandBehavior(textBox);

			if (e.NewValue != null)
			{
				behavior.Command = (ICommand)e.NewValue;
				textBox.SetValue(EnterKeyCommandBehaviorProperty, behavior);
				behavior.Attach();
			}
			else
			{
				textBox.SetValue(EnterKeyCommandBehaviorProperty, null);
				behavior.Detach();
			}
		}

		/// <summary>
		/// Retrieves the behavior if it has been previously associated, or creates a new behavior instance.
		/// </summary>
		static EnterKeyCommandBehavior GetOrCreateEnterKeyCommandBehavior(TextBox target)
		{
			var behavior = target.GetValue(EnterKeyCommandBehaviorProperty) as EnterKeyCommandBehavior;

			if (behavior == null)
			{
				behavior = new EnterKeyCommandBehavior {Target = target};
			}

			return behavior;
		}

		public class EnterKeyCommandBehavior
		{
			public TextBox Target { get; set; }
			public ICommand Command { get; set; }

			public void Attach()
			{
				Target.KeyDown += OnKeyDown;
			}

			public void Detach()
			{
				Target.KeyDown -= OnKeyDown;
			}

			void OnKeyDown(object sender, KeyEventArgs e)
			{
				if (e.Key == Key.Enter)
				{
					Page parent = Target.GetVisualAncestors<Page>().FirstOrDefault();
					if (parent != null)
					{
						parent.Focus();
					}

					Command.Execute(Target.Text);
					if ((bool)Target.GetValue(TextClearedProperty))
					{
						Target.Text = string.Empty;
					}
				}
			}
		}

		#endregion
	}

	public sealed class WatermarkBehavior : Behavior<TextBox>
	{
		const string nonBreakingSpace = "\x00A0";
		Brush foregroundBrush;

		/// <summary>
		/// Text property - set in XAML
		/// </summary>
		public static readonly DependencyProperty WatermarkTextProperty =
			DependencyProperty.RegisterAttached("WatermarkText", typeof(string), typeof(WatermarkBehavior),
												new PropertyMetadata(null, HandleWatermarkTextChanged));

		public string WatermarkText
		{
			get
			{
				return (string)GetValue(WatermarkTextProperty);
			}
			set
			{
				SetValue(WatermarkTextProperty, value);
			}
		}

		/// <summary>
		/// Backing storage for IsWatermarkVisible property.
		/// </summary>
		public static readonly DependencyProperty WatermarkVisibleProperty =
			DependencyProperty.Register("WatermarkVisible", typeof(bool), typeof(WatermarkBehavior),
										new PropertyMetadata(default(bool), HandleWatermarkVisibilityChanged));

		static void HandleWatermarkVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var behavior = (WatermarkBehavior)d;
			if ((bool)e.NewValue)
			{
				if (!behavior.SetWatermarkTextIfTextboxIsEmpty())
				{
					behavior.WatermarkVisible = false;
				}
			}
			else
			{
				behavior.RemoveWatermarkText();
			}
		}

		/// <summary>
		/// Returns whether the watermark is currently visible.  This can be databound to test within a ViewModel.
		/// This should be used to determine if the Text property of the TextBox is valid or not.
		/// </summary>
		public bool WatermarkVisible
		{
			get
			{
				return (bool)GetValue(WatermarkVisibleProperty);
			}
			set
			{
				SetValue(WatermarkVisibleProperty, value);
			}
		}

		public static bool IsWatermarkVisible(TextBox textBox)
		{
			var behaviors = Interaction.GetBehaviors(textBox);
			var behavior = (WatermarkBehavior)behaviors.FirstOrDefault(b => b is WatermarkBehavior);

			if (behavior == null)
			{
				behavior = (WatermarkBehavior)textBox.GetValue(TextBoxBehaviors.WatermarkBehaviorProperty);
			}

			if (behavior != null && behavior.AssociatedObject == textBox)
			{
				return behavior.WatermarkVisible;
			}
			
			return false;
		}

		public static string GetText(TextBox textBox)
		{
			return (string)textBox.GetValue(WatermarkTextProperty);
		}

		public static void SetText(TextBox textBox, string text)
		{
			textBox.SetValue(WatermarkTextProperty, text);
		}

		/// <summary>
		/// The color of the watermark text. Default is gray.
		/// </summary>
		public static readonly DependencyProperty WatermarkColorProperty =
			DependencyProperty.Register("WatermarkColor", typeof(Brush), typeof(WatermarkBehavior),
							new PropertyMetadata(new SolidColorBrush(Colors.Gray), HandleWaterMarkColorChanged));

		static void HandleWaterMarkColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var behavior = (WatermarkBehavior)d;
			if (behavior.AssociatedObject != null)
			{
				behavior.SetTextBoxForgroundIfWatermarkVisible();
			}
		}

		public Brush WatermarkColor
		{
			get
			{
				return (Brush)GetValue(WatermarkColorProperty);
			}
			set
			{
				SetValue(WatermarkColorProperty, value);
			}
		}

		/// <summary>
		/// This is called when the watermark text is changed on either a WatermarkBehavior
		/// or on a TextBox directly to attach/detach the behavior.
		/// </summary>
		/// <param name="d"></param>
		/// <param name="e"></param>
		static void HandleWatermarkTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var behavior = d as WatermarkBehavior;
			if (behavior != null)
			{
				if (behavior.AssociatedObject != null)
				{
					behavior.WatermarkVisible = true;
				}
			}
			// Attach/Detach request
			else
			{
				TextBox textBox = d as TextBox;
				if (textBox != null)
				{
					string text = (e.NewValue ?? string.Empty).ToString();
					BehaviorCollection behaviors = Interaction.GetBehaviors(textBox);
					var watermarkBehavior = (WatermarkBehavior)behaviors.FirstOrDefault(b => b is WatermarkBehavior);

					if (string.IsNullOrWhiteSpace(text))
					{
						if (watermarkBehavior != null)
						{
							behaviors.Remove(watermarkBehavior);
						}
					}
					else
					{
						if (watermarkBehavior == null)
						{
							watermarkBehavior = new WatermarkBehavior { WatermarkText = text };
							behaviors.Add(watermarkBehavior);
						}
						else
						{
							watermarkBehavior.WatermarkText = text;
						}
					}

				}
			}
		}

		protected override void OnAttached()
		{
			AssociatedObject.GotFocus -= HandleTextBoxGotFocus;
			AssociatedObject.GotFocus += HandleTextBoxGotFocus;
			AssociatedObject.LostFocus -= HandleTextBoxLostFocus;
			AssociatedObject.LostFocus += HandleTextBoxLostFocus;
			AssociatedObject.TextChanged -= HandleTextBoxTextChanged;
			AssociatedObject.TextChanged += HandleTextBoxTextChanged;
			AssociatedObject.Loaded -= HandleTextBoxLoaded;
			AssociatedObject.Loaded += HandleTextBoxLoaded;

			if (AssociatedObject.Parent != null)
			{
				WatermarkVisible = true;
			}
		}

		/// <summary>
		/// Called when the TextBox is loaded - we associate the watermark
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="routedEventArgs"></param>
		void HandleTextBoxLoaded(object sender, RoutedEventArgs routedEventArgs)
		{
			WatermarkVisible = true;
		}

		/// <summary>
		/// Override called when behavior is detached
		/// </summary>
		protected override void OnDetaching()
		{
			AssociatedObject.GotFocus -= HandleTextBoxGotFocus;
			AssociatedObject.LostFocus -= HandleTextBoxLostFocus;
			AssociatedObject.TextChanged -= HandleTextBoxTextChanged;

			WatermarkVisible = false;
		}

		bool SetWatermarkTextIfTextboxIsEmpty()
		{
			if (string.IsNullOrEmpty(AssociatedObject.Text))
			{
				Brush textBoxForeground = AssociatedObject.Foreground;
				foregroundBrush = textBoxForeground != Control.ForegroundProperty.GetMetadata(typeof(TextBox)).DefaultValue
									? textBoxForeground : null;
				AssociatedObject.Foreground = this.WatermarkColor;
				AssociatedObject.Text = this.WatermarkText + nonBreakingSpace;
				return true;
			}

			return false;
		}

		void SetTextBoxForgroundIfWatermarkVisible()
		{
			if (!string.IsNullOrEmpty(AssociatedObject.Text))
			{
				if (WatermarkVisible)
				{
					AssociatedObject.Foreground = this.WatermarkColor;
				}
			}
		}

		bool RemoveWatermarkText()
		{
			if (!string.IsNullOrEmpty(AssociatedObject.Text))
			{
				if (AssociatedObject.Text == WatermarkText + nonBreakingSpace)
				{
					if (foregroundBrush != null)
					{
						AssociatedObject.Foreground = foregroundBrush;
					}
					else
					{
						AssociatedObject.ClearValue(Control.ForegroundProperty);
					}
					AssociatedObject.Text = string.Empty;
					return true;
				}
			}

			return false;
		}

		void HandleTextBoxLostFocus(object sender, RoutedEventArgs routedEventArgs)
		{
			WatermarkVisible = true;
		}

		/// <summary>
		/// Removes the watermark.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="routedEventArgs"></param>
		void HandleTextBoxGotFocus(object sender, RoutedEventArgs routedEventArgs)
		{
			WatermarkVisible = false;
		}

		/// <summary>
		/// Helper method to determine our associated element, or any CHILD of our parent has focus.
		/// </summary>
		/// <returns></returns>
		bool IsTextBoxFocussed()
		{
			var focusedElement = FocusManager.GetFocusedElement() as DependencyObject;
			bool focused = (focusedElement == AssociatedObject)
							|| (focusedElement != null 
									&& focusedElement.GetVisualAncestors<TextBox>(
															v => v == AssociatedObject).Any());

			return focused;
		}

		void HandleTextBoxTextChanged(object sender, TextChangedEventArgs e)
		{
			if (!IsTextBoxFocussed() && !WatermarkVisible)
			{
				if (string.IsNullOrEmpty(AssociatedObject.Text))
				{
					WatermarkVisible = true;
				}
				else
				{
					if (foregroundBrush != null)
					{
						AssociatedObject.Foreground = foregroundBrush;
					}
					else
					{
						AssociatedObject.ClearValue(Control.ForegroundProperty);
					}
				}
			}
		}
	}

}
